
前情提要
Day 17 的 3-2-1-1-0 對照表留下了兩個紅燈:異地的 1 與 不可變的 1。
次要 NAS 與主 NAS 位於同一機房、同一電力迴路、同一條對外光纖後面,一旦遭遇火災或嚴重水患,兩台設備會同時損毀。另一方面,Day 16 的本機不可變性僅鎖定新寫入的 pack 檔,取得 NAS 完整最高權限的人依然能一鍵抹除整個儲存庫。
今天我們將透過一個雲端 Bucket 同時把這兩個紅燈轉為綠燈,並將每個月的雲端帳單精算至小數點後兩位。
今天主要的任務是開好 Bucket、套用保留政策(Retention Policy)、設定排程將資料送上去、再試著執行刪除做防禦驗證。
結果實際執行時,碰到了這幾個問題。
asia-east1)的數字,且計費基底為 GiB 而非 GB。我會逐步說明如何測試、驗證,並給出最具經濟效益的異地備份實務解法。
以 Day17 的內容來說,次要 NAS 的 0.9 TB 儲存上限逼出了第一次資料分級,而雲端物件儲存複雜的計費模型,會再逼出第二次的資料分級,不是所有的資料都要上雲端的物件儲存。
物件儲存的每月帳單主要由五個方面組成:儲存容量、API 請求次數(Class A / B)、最低保存天數限制、資料取回費(Retrieval) 以及 網路流出費(Egress)。每個資料集的讀寫特性不同,在各項目的敏感度也完全不同。
以下成本試算由客製腳本 offload-cost.py 自動計算,基準設定為:模擬存放 12 個月、發生 1 次全量災難還原、上傳頻寬依中華電信 500M 線路實測 85%(約 53 MiB/s)計。檔案大小採用上傳後 rclone size 的真實測得值(單位 GiB)。
價格先更正一次
大家上網看的網路教學文章常引用 GCS 官網最便宜的美版價格:Coldline 每 GB $0.004、Archive $0.0012。
但實際上呢,只要透過 Cloud Billing Catalog API 直接查詢台灣機房(asia-east1)的專屬 SKU:Coldline 實為 $0.005/GiB、Archive 實為 $0.0015/GiB,兩者都比美區高出 25%,且計價單位是 GiB 而非 GB。
另外,亞太區 Egress 每月前 100 GiB 免費,因此像Music這樣的小資料集還原時頻寬完全免費。環境中的prices.json均標記了精確 SKU 代號與查核基準日(2026-10-02)。
| 資料集 | 上傳大小 | 物件總數 | 選定儲存類別 | 月儲存費 | 首次上傳 PUT | 還原一次費用 | 一年含一次還原總成本 | 最終決策 |
|---|---|---|---|---|---|---|---|---|
| Public/Music | 4.7 GiB | 128 | Standard 轉 Nearline | $0.05 ~ $0.09 | $0.00 | $0.00 | $0.68 ~ $1.25 | 上雲 |
| Public/Wordpress | 17.6 GiB | 31 | Standard 轉 Nearline | $0.18 ~ $0.35 | $0.00 | $0.00 ~ $0.18 | $2.52 ~ $4.69 | 上雲 |
| Container | 10.5 GiB | 45,042 | Standard 轉 Nearline | $0.10 ~ $0.21 | $0.23 ~ $0.45 | $0.02 ~ $0.15 | $3.06 ~ $4.26 | 上雲 |
| Container | 10.5 GiB | 45,042 | Archive | $0.02 | $2.25 | $2.78 | $9.32 | 不選 |
| HDP_Business | 901 GiB | 4,626 | Coldline | $4.51 | $0.09 | $114.23 | $175.67 | 上雲 |
| HDP_Business | 901 GiB | 4,626 | Archive | $1.35 | $0.23 | $141.45 | $167.07 | 不選 |
| AI Models (權重庫) | 約 2.9 TiB | - | Archive | $4.39 | - | $466.00 | $53 + 還原費 | 不上雲 |
註:Music、Wordpress、Container 的月費區間,下限為 30 天後經生命週期自動轉入 Nearline 的穩定態,上限為全程維持 Standard。
稀疏檔(Sparse File)讓傳輸量膨脹 3.4 倍
HDP_Business 在本地 ZFS 壓縮儲存池中實質僅佔用 262 GiB,但傳上雲端卻顯示為 901 GiB。原因是 HDP 產出的 VM 虛擬磁碟映像採用稀疏配置,邏輯大小遠大於實體寫入區塊。標準 rclone 傳輸時預設不解譯檔案洞(holes),將未配置的空洞全部補零照常傳輸,上傳就會更耗時,一年綜合儲存費用直接從原本預估的 $47.84 暴增至 $175.67。
結論:上傳大檔前,務必比對 du -sh <path> 與 du -sh --apparent-size <path> 的差距。
Archive 取回費過高,容災演練一次就破功
若將 HDP_Business 存放於 Archive,平時看似比 Coldline 每年省約 $9,但這是建立在「整年僅容許還原 1 次」的嚴苛假設。Archive 的資料取回費為 $0.05/GiB,是 Coldline($0.02/GiB)的 2.5 倍。只要一年內進行兩次還原演練,Coldline 一年總花費為 $289.90,而 Archive 會衝到 $308.52。加上 HDP 備份每 30 天滾動輪替,Archive 具有 365 天最低計費保存期,提早刪除仍需按年收費。因此具備演練需求的備份集應果斷選擇 Coldline。
大量小檔案的 Class A 請求費陷阱
Container 資料夾大小僅 10.5 GiB,在任何儲存層級的純容量月費都低於 1 美元。但其內部散落了 45,042 個微型檔案。Archive 類別的 Class A(PUT/LIST)操作每千次收費高達 $0.05,單次初次上傳的 API 呼叫費就高達 $2.25,相當於它在 Standard 存滿一整年的純容量費用。
不可替代性 vs 重建成本
2.9 TiB 的開源 AI 模型權重若放 Archive,一年儲存費約 $53,但一旦發生災難需要取回時,單次取回加流量費高達 $466。反觀透過中華電信 500M 下載,從 Hugging Face 原廠重新拉回耗時約 16 小時,頻寬成本為 $0。因此策略調整為不上雲,僅備份其下載清單與設定稿本(redownload-plan.md)。
雲端平台橫向對比:
合計異地真實上雲資料量約 934 GiB。GCS(asia-east1)一年總成本約 $182 ~ $186。
若改選 Linode Object Storage(Akamai 亞太區),基本月費 $5(含 250 GB 與 1 TB 免費流出),超量每 GB $0.02,同樣資料量一年約 $241。Linode 的優點是無冷熱層級拆分、無取回費,GCS 的優勢則在於在地機房低延遲、成熟的 WORM 保留政策鎖定,以及實測高達 2.7 倍的穩定上傳輸送率。
如果要更便宜的物件儲存,如果你是用 QNAP NAS 的話,他們有 myQNAPcloud Object 這種 S3 相容的雲端物件儲存方案可直接內建套件採用,如果是自己的程式也可以直接和 S3 相容,程式中更改端點和存取金鑰就會動了。沒有額外的傳輸和 API 要求費,確實會省很多錢,但我沒有購買帳號,所以這次沒有測試到這一塊。
在本系列中,我們已經在不同層級建置了三種「不可變」特性。將它們攤開對比,才能釐清防禦邊界:
| 層級 | 實作機制 | 能防禦的威脅 | 防禦失效情境 |
|---|---|---|---|
| Day 16 HDP 本機端 | 針對新寫入 pack 設唯讀屬性,atime 設到期日 |
客戶端勒索病毒、人為誤刪備份目錄 | 取得 NAS 管理員權限、儲存池硬體損毀 |
| Day 17 次要 NAS 快照 | ZFS 唯讀快照(Read-only Snapshot),保留 14 代 | 主 NAS 整機損毀、同步過去損毀檔案 | 次要 NAS 被取得最高權限、實體機房損毀 |
| 本日 GCS 保留政策 | Bucket 級保留政策(Retention Policy)+ 鎖定(Bucket Lock) | 內網全面淪陷、儲存設備或 NAS 管理員帳號失守或外流、上傳憑證外洩、專案 Owner 誤刪 | 保留政策鎖定前 Owner 手動解鎖、雲端帳號欠費關閉 |
| 本日 S3 Object Lock | Linode Bucket 啟用 COMPLIANCE 模式 | 同上,全權限 Admin API Key 也無法強制刪除 | 雲端帳號欠費關閉 |
在 GCS 啟用保留政策前,必須同時啟用 物件版本控制(Object Versioning)。
保留政策強制「受保護期間內禁止刪除或覆寫現有 Generation」。若未開啟版本控制,同步工具在覆寫任何更動過的檔案時會直接拋出異常而中斷,開啟版本控制後,覆寫操作會轉化為建立新的 Generation,舊版本自動降為 noncurrent,並在保留期滿前持續受到保護。
透過 gcs-bucket.sh create 可一次將版本、保留政策、生命週期、公有存取阻擋與軟刪除週期(歸零以防重複計費)設定完成:
default_storage_class: STANDARD
location: ASIA-EAST1
public_access_prevention: enforced
retention_policy:
effectiveTime: '2026-10-01T17:34:12.917000+00:00'
retentionPeriod: '604800'
soft_delete_policy:
retentionDurationSeconds: '0'
uniform_bucket_level_access: true
versioning_enabled: true
注意:retention_policy 下方未出現 isLocked: true,代表處於演練驗證期(設定保留 7 天)。待演練驗證無誤、準備正式上線時,再改為 30 天並執行 gcs-bucket.sh lock。一旦鎖定,任何人都無法縮短保留期或刪除 Bucket,專案也會被強制加上 Lien 保護。
在我原本的構想中,為了徹底防護外洩風險,我打算替上傳 Service Account 嚴格限縮權限:只給予 storage.objects.create、get、list,刻意拔除 delete。
但在首次端對端測試時便被推翻:當工具對同一個路徑發起第二次備份覆寫時,GCS API 直接回傳 403 AccessDenied。
這是因為 GCS 的架構中,覆寫既有物件並轉入 Versioning 的行為,底層必須具備 storage.objects.delete 授權。若缺少該權限,日常增量同步只要遇到任何檔案變更就會整批中斷,使備份形同一次性寫入。
最終定案的上傳自訂角色(nasOffloadWriter)補回了 storage.objects.delete,總計 9 項權限:

這次透過 offload-drill.sh lock-test 實際發送 8 種破壞性請求,驗證保留政策的最後防線:
| 測試身分 | 發起動作 | API 原文回應 | 資料實體是否依然留存? |
|---|---|---|---|
| 上傳身分(無 delete) | 覆寫既有檔案 | 403 AccessDenied |
在(但常規備份流程會中斷報錯) |
| 上傳身分(無 delete) | 刪除檔案 | 403 AccessDenied |
在(受 IAM 阻擋) |
| 上傳身分(有 delete) | 刪除檔案 | 200 OK(成功) |
在(當前版本標記刪除,轉入 noncurrent) |
| 上傳身分(有 delete) | 刪除特定 generation | 403 RetentionPolicyNotMet |
在(底層封鎖,完全刪不掉) |
| GCP 專案 Owner | 刪除檔案 | 200 OK(成功) |
在(轉入 noncurrent) |
| GCP 專案 Owner | 刪除特定 generation | 403 ... subject to bucket's retention policy |
在(保留政策強制拒絕) |
| Linode 全權限金鑰 | 刪除檔案 | 200 OK(成功) |
在(產生 Delete Marker) |
| Linode 全權限金鑰 | 刪除特定 Version ID | AccessDenied: forbidden by object lock |
在(Object Lock 強制防護) |
這份實測的核心結論在於:一般刪除操作成功,不代表資料真的消失。
未指定 Generation 的常規刪除只是將檔案轉入歷史版本,真正能摧毀資料的「指定 Generation / Version ID 刪除」,會被保留政策徹底阻擋。即使勒索病毒拿到了這把金鑰,最多只能讓 Bucket 外觀看似被清空,所有歷史版本依舊安然存放在雲端,管理者只需透過歷史版本即可完整拉回。
在規劃雲端異地架構時,如果只看「儲存每 GB 多少錢」,帳單出來時一定會超支。以下是本次演練整理出的真實隱藏成本清單:
| 陷阱項目 | 實測衝擊數據 | 實際架構影響與應對作為 |
|---|---|---|
| 稀疏檔(Sparse File) | 實際用量 262 GiB,上傳佔用 901 GiB | 傳輸時間、儲存費、取回費全部放大 3.4 倍。試算前務必確認邏輯與實體大小。 |
| 高頻 PUT API 請求 | Archive 每千次 $0.05,Standard 為 $0.005 | 微型檔案多的資料集(如 Container),首次寫入 API 成本便相當於一年容量租金。 |
| 最低計費天數限制 | Nearline 30 天、Coldline 90 天、Archive 365 天 | 頻繁滾動淘汰的備份切勿放入 Archive,否則提早刪除仍會被按滿期計費。 |
| 取回費與流出流量 | Archive 取回 $0.05/GiB,亞太流量超過 100G 為 $0.12/GiB | 2.9 TiB 模型取回單次逼近 $466,非必要資料應以「重建計畫」替代異地備份。 |
| 在地機房區域價差 | asia-east1 的冷儲存比美國常用基準高出 25% |
試算時必須依據精確 SKU 查詢,避免以通用宣傳頁面估算。 |
| 演練本身的保底費 | 資料受保留政策鎖定,Coldline 具 90 天最低限制 | 演練寫入的 901 GiB 一旦送出,便保證計費三個月(約 $13.5),無法提前清空。 |
| 軟刪除(Soft Delete) | GCS 預設對已刪除物件保留 7 天並持續計費 | 與 WORM 功能重複,建立演練 Bucket 時應明確將其保留秒數設為 0。 |
| 海量小檔傳輸損耗 | 大檔傳輸達 59 MiB/s,4.5 萬個小檔降至 14.8 MiB/s | 相同網路頻寬下,小檔案延遲導致總傳輸時間放大近 4 倍。 |
| 分段上傳前置雜湊 | rclone 切片上傳前需讀完整檔計算 MD5 | 本地 NFS 出現 400 MiB/s 讀取而對外流量為 0,屬正常校驗現象,非卡死。 |
在實務架構中,我們將 QNAP 內建的 HBS 3 與在專屬 Linux 主機上執行的 rclone 進行了同場對比:
| 比較維度 | QNAP HBS 3 雲端同步 | Linux 主機掛載 NFS 跑 rclone |
|---|---|---|
| 執行環境 | NAS 本機原生運作,不耗費額外主機 | 外部 Linux 節點,需自行管理 Cron 排程 |
| 傳輸效能(Music) | 59.0 MiB/s(貼近 500M 頻寬極限) | 58.2 MiB/s(貼近 500M 頻寬極限) |
| GCS 驗證支援 | 僅支援 OAuth、P12、JSON Key,不收 HMAC | 完整支援 HMAC、S3 API、GCS API 與 Token |
| 進階功能限制 | 雲端同步模式強行停用快照與稀疏檔偵測 | 可透過各類 Flag 自訂,但無法跨 NFS 識別稀疏檔 |
storage.googleapis.com),初始連線時會跳出 cloud_unauthorized。原因在於 HBS 3 會預先調用 ListBuckets 驗證帳號有效性,而如果上傳 Service Account 權限僅綁定在單一 Bucket 上,驗證就會被拒。我們必須額外建立一個僅包含 storage.buckets.list 權限的自訂角色(nasOffloadLister)並綁定在專案層級,HBS 3 才能成功建立連線。
虛擬目錄標記(Directory Markers)需求
HBS 3 帳號就緒後,初次同步在 0.3 秒內迅速噴出 Failed to locate the destination folder。物件儲存本質上是 Flat 鍵值結構,但 HBS 3 堅持遠端目的地目錄必須存在。解法是先透過 rclone mkdir --s3-directory-markers gcs:bucket/prefix 建立一個空的資料夾標記,HBS 3 才能順利對齊。
執行中檔案的寫入鎖定衝突
在測試 Container 目錄備份時,由於 SQLite 資料庫正在運行,其 WAL、shm 與 lock 檔案在傳輸途中發生變更。rclone 重試時嘗試透過 Server-Side Copy 更新時間戳記,被保留政策回傳 403 阻擋,導致整個任務以 Exit Code 1 結束。後續在腳本中加入 --no-update-modtime 參數,即可順利容忍中途變更。
[演練矩陣]
├── 演練 1: 多資料集端對端上傳速度與特性量測
├── 演練 2: 主 NAS 毀滅性刪除後的異地全量還原
├── 演練 3: 惡意刪除與保留政策防禦驗證
└── 演練 4: Coldline 冷層資料首位元組延遲(TTFB)量測
| 演練項目 | 觸發時間(UTC) | 傳輸目標 | 資料量 | 耗時 | 平均速率 | 結果 | 關鍵觀察 |
|---|---|---|---|---|---|---|---|
| 1a. rclone 上傳 Music | 17:38:09 | GCS Standard | 4,831 MiB | 83s | 58.2 MiB/s | PASS | 128 個物件,跑滿 500M 線路 |
| 1b. HBS 3 上傳 Music | 23:41:32 | GCS Standard | 4,831 MiB | 82s | 59.0 MiB/s | PASS | 97 個物件(預設忽略隱藏檔) |
| 1c. rclone 上傳 Wordpress | 17:41:22 | GCS Standard | 17,969 MiB | 301s | 59.7 MiB/s | PASS | 31 個大物件,速度平穩 |
| 1c. rclone 上傳 Container | 17:47:25 | GCS Standard | 10,755 MiB | 728s | 14.8 MiB/s | WARN | 4.5 萬微型檔案,速率驟降至 1/4 |
| 1c. rclone 上傳 HDP 映像 | 18:03:13 | GCS Coldline | 922,892 MiB | 15,842s | 58.3 MiB/s | PASS | 耗時 4.4 小時,稀疏檔全量上傳 |
| 1d. rclone 上傳至 Linode | 23:32:41 | Linode 東京 | 4,831 MiB | 223s | 21.7 MiB/s | PASS | 跨海延遲導致 8 線程填不滿頻寬 |
| 2. 從 GCS 還原 Music | 23:43:59 | 主 NAS | 4,808 MiB | 92s | 52.3 MiB/s | PASS | 雜湊校驗 97/97 通過,總耗時 102s |
| 3. 物件防刪除測試 | 17:47-17:59 | GCS / Linode | - | - | - | PASS | 8 種情境驗證,資料全數安全留存 |
| 4. Coldline 喚醒延遲 | 23:32:33 | GCS Coldline | - | - | 141 ms | PASS | 首位元組幾乎即時(Standard: 160ms) |
還原時的權限與擁有者細節(演練 2)
在主 NAS 上將 Music 徹底刪除後,透過 offload-drill.sh restore 自 GCS 完整拉回。檔案本身與修改時間完整復原,但檔案的所屬權限(UID/GID)發生了變化:rclone 會以執行當下的 Linux 帳號寫入,原本歸屬於 users 群組的檔案變成了執行者的私有群組,原屬於 root 的快取縮圖也換了擁有者。因此災難復原程序手冊中必須補上一道指令:
chgrp -R users /mnt/music
否則上層多媒體服務可能因權限不足而無法讀取。
Coldline 無解凍等待期(演練 4)
GCS 的 Coldline 與 AWS S3 Glacier Deep Archive 不同,它不需要長達數小時的解凍等待。實測發起讀取請求到接收到第一個 Byte(TTFB),中位數僅需 141 毫秒,與 Standard 的 160 毫秒毫無差異。這代表當真實災難降臨時,使用 GCS Coldline 能夠以「分鐘級」迅速展開復原作業。它的代價完全呈現在取回帳單上,而不是時間延誤上。
歷經 Day 16 的本機不可變、Day 17 的區域次要快照,到今天的雲端異地 WORM 實作,本 IThome 鐵人賽系列的 3-2-1-1-0 架構終於全員轉為綠燈:
| 準則指標 | 規範要求 | 本地與雲端架構配置狀態 | 最終驗證狀態 |
|---|---|---|---|
| 3 | 三份完整資料副本 | 1. 主 NAS 儲存池2. 次要 NAS 複本儲存庫3. GCS(asia-east1)異地儲存桶 |
達成 |
| 2 | 兩種以上不同儲存媒體 | 本地專用 Enterprise SAS/SATA HDD + 雲端高可用分散式物件儲存系統 | 達成 |
| 1 | 一份異地實體複本 | 放置於 Google 彰濱資料中心,遠離本地辦公室機房環境 | 達成 |
| +1 | 一份離線或具備不可變特性 | GCS 啟用 Bucket Retention Policy(演練期 7 天,正式期 30 天鎖定),實測專案 Owner 亦無法強行抹除 | 達成 |
| +0 | 零錯誤還原驗證 | 演練 2 進行真實破壞性還原,SHA-256 Manifest 97/97 檔案全數校驗吻合 | 達成 |
在現代維運體系中,任何一次異地同步都不是設完就放著不管。
今天在雲端專案中建置的 Bucket、自訂 IAM 角色、Service Account、HMAC 金鑰、生命週期轉移規則,全部都是基礎架構中的受控變更(Day 26)。
透過 gcs-bucket.sh show 的組態輸出、drills.jsonl 中每一次破壞性測試的 API 回應原文,以及真實的 Cloud Billing 帳單,都將作為稽核報告的關鍵佐證(Day 27)。將實際帳單與 offload-cost.py 的預算模型相互比對、持續修正誤差,才算完成真正的工程閉環。
本篇所有操作程式碼均已開源收錄到 cloud-offload-drill 這個專案中,可透過以下流程在自己的環境中重現驗證:
# 取得自動化測試工具包
git clone https://github.com/ivanusto/cloud-offload-drill && cd cloud-offload-drill
# 1. 執行容量與成本精算試算
./offload-cost.py
./offload-cost.py one --gib 262 --files 4400 --change-gib 40
# 2. 建立 Bucket 與最小權限帳號(演練期設定 7 天保護)
./gcs-bucket.sh create my-project nas-offsite-drill asia-east1 7d
./gcs-bucket.sh sa my-project nas-offsite-drill nas-offload
./gcs-bucket.sh lister my-project nas-offload@my-project.iam.gserviceaccount.com # 僅 HBS 3 介接需配置
./gcs-bucket.sh hmac my-project nas-offload@my-project.iam.gserviceaccount.com
./gcs-bucket.sh show nas-offsite-drill > bucket-evidence.yaml
# 3. 檢查本地資料是否存在稀疏檔膨脹風險
du -sh /mnt/hdp
du -sh --apparent-size /mnt/hdp
# 4. 執行上傳演練
./offload-drill.sh upload /mnt/music gcs:nas-offsite-drill/Music --label "1a. rclone 上傳 Music"
# 5. 執行真實復原演練並比對雜湊清單
./offload-drill.sh restore gcs:nas-offsite-drill/Music /mnt/music --manifest music.sha256 --label "2. 還原 Music"
# 6. 發起破壞性刪除,驗證保留政策防護力
./offload-drill.sh lock-test gcs:nas-offsite-drill/drill/canary/payload.bin --label "3a. 上傳身分"
./hmac-rm-generation.py nas-offsite-drill/drill/canary/payload.bin <GENERATION_ID>
# 以專案管理者身分嘗試強制刪除指定版本
RCLONE_GCS_ACCESS_TOKEN=$(gcloud auth print-access-token) \
./offload-drill.sh lock-test gcs-owner:nas-offsite-drill/drill/canary/payload.bin \
--gs gs://nas-offsite-drill/drill/canary/payload.bin --label "3b. Owner"
# 7. 量測冷層讀取延遲時間
./offload-drill.sh latency gcs:nas-offsite-drill/HDP_Business/some.pack --repeat 5 --label "4. Coldline 首位元組"
退出狀態碼(Exit Code)規範:
restore:0代表 Manifest 雜湊全數比對成功,4代表出現檔案遺失或校驗錯誤。lock-test:0代表防護成功(刪除請求被拒、僅留下 Delete Marker 或保留於 noncurrent 世代),1代表實體資料被徹底刪除(重大安全性防禦失效)。
第二階段「儲存與災難復原實戰」到今天正式告一段落。從 Day 9 的硬體容量規劃到 Day 18 的異地不可變物件儲存,已經努力為所有的節點、儲存池與備份鏈路都建立了可反覆驗證的程式碼與演練證據。
Day 19 起將邁入第三階段:全面可觀測性(Observability)。
接下來將從底層硬體指標收集切入,涵蓋 DGX Spark 的溫度與 GPU 記憶體水位、NAS 儲存池的 Snapshot 佔用趨勢,以及 Proxmox VE 的 HA 叢集健康狀態,全部整合進同一個時序資料庫,為 Day 20 打造一目了然的戰情儀表板。